use crate::{
    ast::Expression,
    error::{CompilerError, Result},
    llvm_backend::LLVMBackend,
};
use inkwell::{values::BasicValueEnum, AddressSpace, IntPredicate};

impl<'ctx> LLVMBackend<'ctx> {

    /// Generate string.contains(substring) method call
    /// Returns true if the string contains the substring
    ///
    /// Example: "hello world".contains("world") returns true
    pub(in crate::llvm_backend) fn generate_string_contains_method(
        &mut self,
        var_name: &str,
        arguments: &[Expression],
    ) -> Result<BasicValueEnum<'ctx>> {
        // Validate arguments
        if arguments.len() != 1 {
            return Err(CompilerError::internal(
                "contains() requires exactly one argument (substring: string)",
            ));
        }

        // Get the string pointer
        let string_ptr = if let Some(var_value) = self.variable_map.get(var_name) {
            var_value.into_pointer_value()
        } else {
            return Err(CompilerError::internal(&format!(
                "Undefined variable: {}",
                var_name
            )));
        };

        // Load the string pointer value (i8*)
        let i8_ptr_type = self.context.i8_type().ptr_type(AddressSpace::default());
        let string_value = self
            .builder
            .build_load(i8_ptr_type, string_ptr, "string_val")?;
        let haystack = string_value.into_pointer_value();

        // Get the substring argument
        let substr_value = self.generate_expression(&arguments[0])?;
        let needle = substr_value.into_pointer_value();

        // Use strstr function to check if substring exists
        let strstr_fn_type = i8_ptr_type.fn_type(&[i8_ptr_type.into(), i8_ptr_type.into()], false);
        let strstr_fn = if let Some(func) = self.module.get_function("strstr") {
            func
        } else {
            self.module.add_function("strstr", strstr_fn_type, None)
        };

        // Call strstr(haystack, needle)
        // Returns NULL if not found, non-NULL pointer if found
        let result = self
            .builder
            .build_call(
                strstr_fn,
                &[haystack.into(), needle.into()],
                "strstr_result",
            )?
            .try_as_basic_value()
            .left()
            .unwrap()
            .into_pointer_value();

        // Check if result is not NULL
        let contains = self.builder.build_is_not_null(result, "contains")?;

        Ok(contains.into())
    }


    /// Generate string.starts_with(prefix) method call
    /// Returns true if the string starts with the given prefix
    ///
    /// Example: "hello world".starts_with("hello") returns true
    pub(in crate::llvm_backend) fn generate_string_starts_with_method(
        &mut self,
        var_name: &str,
        arguments: &[Expression],
    ) -> Result<BasicValueEnum<'ctx>> {
        // Validate arguments
        if arguments.len() != 1 {
            return Err(CompilerError::internal(
                "starts_with() requires exactly one argument (prefix: string)",
            ));
        }

        // Get the string pointer
        let string_ptr = if let Some(var_value) = self.variable_map.get(var_name) {
            var_value.into_pointer_value()
        } else {
            return Err(CompilerError::internal(&format!(
                "Undefined variable: {}",
                var_name
            )));
        };

        // Load the string pointer value (i8*)
        let i8_ptr_type = self.context.i8_type().ptr_type(AddressSpace::default());
        let string_value = self
            .builder
            .build_load(i8_ptr_type, string_ptr, "string_val")?;
        let string_ptr_val = string_value.into_pointer_value();

        // Get the prefix argument
        let prefix_value = self.generate_expression(&arguments[0])?;
        let prefix_ptr = prefix_value.into_pointer_value();

        // Use strncmp function to compare the beginning of the string
        // First, get prefix length using strlen
        let i32_type = self.context.i32_type();
        let i64_type = self.context.i64_type();

        let strlen_fn_type = i32_type.fn_type(&[i8_ptr_type.into()], false);
        let strlen_fn = if let Some(func) = self.module.get_function("strlen") {
            func
        } else {
            self.module.add_function("strlen", strlen_fn_type, None)
        };

        // Get prefix length
        let prefix_len_i32 = self
            .builder
            .build_call(strlen_fn, &[prefix_ptr.into()], "prefix_len_i32")?
            .try_as_basic_value()
            .left()
            .unwrap()
            .into_int_value();
        let prefix_len = self
            .builder
            .build_int_z_extend(prefix_len_i32, i64_type, "prefix_len")?;

        // Use strncmp to compare first prefix_len bytes
        // strncmp(s1, s2, n) returns 0 if first n characters match
        let strncmp_fn_type = i32_type.fn_type(
            &[i8_ptr_type.into(), i8_ptr_type.into(), i64_type.into()],
            false,
        );
        let strncmp_fn = if let Some(func) = self.module.get_function("strncmp") {
            func
        } else {
            self.module.add_function("strncmp", strncmp_fn_type, None)
        };

        // Call strncmp(string, prefix, prefix_len)
        let cmp_result = self
            .builder
            .build_call(
                strncmp_fn,
                &[string_ptr_val.into(), prefix_ptr.into(), prefix_len.into()],
                "strncmp_result",
            )?
            .try_as_basic_value()
            .left()
            .unwrap()
            .into_int_value();

        // Check if result == 0 (strings match)
        let starts_with = self.builder.build_int_compare(
            IntPredicate::EQ,
            cmp_result,
            i32_type.const_int(0, false),
            "starts_with",
        )?;

        Ok(starts_with.into())
    }


    /// Generate string.ends_with(suffix) method call
    /// Returns true if the string ends with the given suffix
    ///
    /// Example: "hello world".ends_with("world") returns true
    pub(in crate::llvm_backend) fn generate_string_ends_with_method(
        &mut self,
        var_name: &str,
        arguments: &[Expression],
    ) -> Result<BasicValueEnum<'ctx>> {
        // Validate arguments
        if arguments.len() != 1 {
            return Err(CompilerError::internal(
                "ends_with() requires exactly one argument (suffix: string)",
            ));
        }

        // Get the string pointer
        let string_ptr = if let Some(var_value) = self.variable_map.get(var_name) {
            var_value.into_pointer_value()
        } else {
            return Err(CompilerError::internal(&format!(
                "Undefined variable: {}",
                var_name
            )));
        };

        // Load the string pointer value (i8*)
        let i8_ptr_type = self.context.i8_type().ptr_type(AddressSpace::default());
        let string_value = self
            .builder
            .build_load(i8_ptr_type, string_ptr, "string_val")?;
        let string_ptr_val = string_value.into_pointer_value();

        // Get the suffix argument
        let suffix_value = self.generate_expression(&arguments[0])?;
        let suffix_ptr = suffix_value.into_pointer_value();

        let i32_type = self.context.i32_type();
        let i64_type = self.context.i64_type();
        let i1_type = self.context.bool_type();

        // Get strlen function
        let strlen_fn_type = i32_type.fn_type(&[i8_ptr_type.into()], false);
        let strlen_fn = if let Some(func) = self.module.get_function("strlen") {
            func
        } else {
            self.module.add_function("strlen", strlen_fn_type, None)
        };

        // Get string length
        let str_len_i32 = self
            .builder
            .build_call(strlen_fn, &[string_ptr_val.into()], "str_len_i32")?
            .try_as_basic_value()
            .left()
            .unwrap()
            .into_int_value();
        let str_len = self
            .builder
            .build_int_z_extend(str_len_i32, i64_type, "str_len")?;

        // Get suffix length
        let suffix_len_i32 = self
            .builder
            .build_call(strlen_fn, &[suffix_ptr.into()], "suffix_len_i32")?
            .try_as_basic_value()
            .left()
            .unwrap()
            .into_int_value();
        let suffix_len = self
            .builder
            .build_int_z_extend(suffix_len_i32, i64_type, "suffix_len")?;

        // Create basic blocks
        let current_func = self.current_function.unwrap();
        let check_block = self
            .context
            .append_basic_block(current_func, "ends_with_check");
        let compare_block = self
            .context
            .append_basic_block(current_func, "ends_with_compare");
        let return_block = self
            .context
            .append_basic_block(current_func, "ends_with_return");

        // Jump to check block
        self.builder.build_unconditional_branch(check_block)?;

        // Check if suffix_len <= str_len
        self.builder.position_at_end(check_block);
        let len_ok = self.builder.build_int_compare(
            IntPredicate::SLE,
            suffix_len,
            str_len,
            "len_ok",
        )?;

        // If suffix is longer than string, return false; otherwise compare
        self.builder
            .build_conditional_branch(len_ok, compare_block, return_block)?;

        // Compare block: calculate offset and use strcmp
        self.builder.position_at_end(compare_block);

        // Calculate start position: str_len - suffix_len
        let offset = self.builder.build_int_sub(str_len, suffix_len, "offset")?;

        // Get pointer to the end part of string: string_ptr + offset
        let i8_type = self.context.i8_type();
        let end_part_ptr = unsafe {
            self.builder
                .build_gep(i8_type, string_ptr_val, &[offset], "end_part_ptr")?
        };

        // Use strcmp to compare the end part with suffix
        let strcmp_fn_type = i32_type.fn_type(&[i8_ptr_type.into(), i8_ptr_type.into()], false);
        let strcmp_fn = if let Some(func) = self.module.get_function("strcmp") {
            func
        } else {
            self.module.add_function("strcmp", strcmp_fn_type, None)
        };

        let cmp_result = self
            .builder
            .build_call(
                strcmp_fn,
                &[end_part_ptr.into(), suffix_ptr.into()],
                "strcmp_result",
            )?
            .try_as_basic_value()
            .left()
            .unwrap()
            .into_int_value();

        // Check if result == 0 (strings match)
        let matches = self.builder.build_int_compare(
            IntPredicate::EQ,
            cmp_result,
            i32_type.const_int(0, false),
            "matches",
        )?;

        self.builder.build_unconditional_branch(return_block)?;

        // Return block with PHI node
        self.builder.position_at_end(return_block);
        let result_phi = self.builder.build_phi(i1_type, "ends_with_result")?;

        // If suffix is too long, return false
        result_phi.add_incoming(&[(&i1_type.const_int(0, false), check_block)]);

        // If comparison succeeded, return comparison result
        result_phi.add_incoming(&[(&matches, compare_block)]);

        Ok(result_phi.as_basic_value())
    }


    /// Generate string.find(substring) method call
    /// Returns the byte position of the first occurrence of substring, or -1 if not found
    ///
    /// Example: "hello world".find("world") returns 6
    /// Example: "hello world".find("xyz") returns -1
    pub(in crate::llvm_backend) fn generate_string_find_method(
        &mut self,
        var_name: &str,
        arguments: &[Expression],
    ) -> Result<BasicValueEnum<'ctx>> {
        // Validate arguments
        if arguments.len() != 1 {
            return Err(CompilerError::internal(
                "find() requires exactly one argument (substring: string)",
            ));
        }

        // Get the string pointer
        let string_ptr = if let Some(var_value) = self.variable_map.get(var_name) {
            var_value.into_pointer_value()
        } else {
            return Err(CompilerError::internal(&format!(
                "Undefined variable: {}",
                var_name
            )));
        };

        // Load the string pointer value (i8*)
        let i8_ptr_type = self.context.i8_type().ptr_type(AddressSpace::default());
        let string_value = self
            .builder
            .build_load(i8_ptr_type, string_ptr, "string_val")?;
        let haystack = string_value.into_pointer_value();

        // Get the substring argument
        let substr_value = self.generate_expression(&arguments[0])?;
        let needle = substr_value.into_pointer_value();

        // Use strstr function to find the substring
        let strstr_fn_type = i8_ptr_type.fn_type(&[i8_ptr_type.into(), i8_ptr_type.into()], false);
        let strstr_fn = if let Some(func) = self.module.get_function("strstr") {
            func
        } else {
            self.module.add_function("strstr", strstr_fn_type, None)
        };

        // Call strstr(haystack, needle)
        // Returns NULL if not found, pointer to first occurrence if found
        let result_ptr = self
            .builder
            .build_call(
                strstr_fn,
                &[haystack.into(), needle.into()],
                "strstr_result",
            )?
            .try_as_basic_value()
            .left()
            .unwrap()
            .into_pointer_value();

        // Check if result is NULL
        let is_null = self.builder.build_is_null(result_ptr, "is_null")?;

        // Create basic blocks
        let current_func = self.current_function.unwrap();
        let found_block = self.context.append_basic_block(current_func, "find_found");
        let not_found_block = self
            .context
            .append_basic_block(current_func, "find_not_found");
        let return_block = self.context.append_basic_block(current_func, "find_return");

        // If NULL, go to not_found_block; otherwise go to found_block
        self.builder
            .build_conditional_branch(is_null, not_found_block, found_block)?;

        // Not found block: prepare to return -1
        self.builder.position_at_end(not_found_block);
        self.builder.build_unconditional_branch(return_block)?;

        // Found block: calculate offset (result_ptr - haystack)
        self.builder.position_at_end(found_block);

        // Convert pointers to integers for subtraction
        let i32_type = self.context.i32_type();
        let i64_type = self.context.i64_type();
        let haystack_int = self
            .builder
            .build_ptr_to_int(haystack, i64_type, "haystack_int")?;
        let result_int = self
            .builder
            .build_ptr_to_int(result_ptr, i64_type, "result_int")?;

        // Calculate offset: result - haystack
        let offset_i64 = self
            .builder
            .build_int_sub(result_int, haystack_int, "offset_i64")?;

        // Truncate to i32 (positions should fit in 32 bits)
        let offset = self
            .builder
            .build_int_truncate(offset_i64, i32_type, "offset")?;

        self.builder.build_unconditional_branch(return_block)?;

        // Return block with PHI node (using i32 for better compatibility with literal comparisons)
        self.builder.position_at_end(return_block);
        let result_phi = self.builder.build_phi(i32_type, "find_result")?;

        // Not found: return -1
        let minus_one = i32_type.const_int((-1i32) as u64, true);
        result_phi.add_incoming(&[(&minus_one, not_found_block)]);

        // Found: return offset
        result_phi.add_incoming(&[(&offset, found_block)]);

        Ok(result_phi.as_basic_value())
    }
}
